#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#define _DEBUG_DUMP

#include "sqplus.h"

#include <sqstdio.h>
#include <sqstdmath.h>
#include <sqstdstring.h>
#include <sqstdaux.h>
#include <sqstdblob.h>

#ifdef _MSC_VER
#define STRLEN(n) _tcslen(n)
#else
#define STRLEN(n) strlen(n)
#endif

HSQUIRRELVM SquirrelVM::_VM = NULL;
SQInteger SquirrelVM::_CallState = -1;
SquirrelObject * SquirrelVM::_root = NULL;

SquirrelError::SquirrelError() {
  const SQChar *s;
  sq_getlasterror( SquirrelVM::_VM );
  sq_getstring( SquirrelVM::_VM, -1, &s );
  if( s ) {
    desc = s;
  } else {
    desc = sqT( "unknown error" );
  }
}

void SquirrelVM::Init( SquirrelInitFlags flags ) {
  _VM = sq_open( 1024 );
  sq_setprintfunc( _VM, SquirrelVM::PrintFunc );
  sq_pushroottable( _VM );
  if( flags & sqifIO ) {
    sqstd_register_iolib( _VM );
  }
  if( flags & sqifBlob ) {
    sqstd_register_bloblib( _VM );
  }
  if( flags & sqifMath ) {
    sqstd_register_mathlib( _VM );
  }
  if( flags & sqifString ) {
    sqstd_register_stringlib( _VM );
  }
  sqstd_seterrorhandlers( _VM );
  _root = new SquirrelObject();
  _root->AttachToStackObject( -1 );
  sq_pop( _VM, 1 );
}

BOOL_T SquirrelVM::Update() {
  return TRUE;
}

void SquirrelVM::Cleanup() {
  sq_pushnull( _VM );
  sq_setroottable( _VM );
}

void SquirrelVM::Shutdown() {
  if( _VM ) {
    Cleanup();
    delete _root;
    _root = NULL;
    HSQUIRRELVM v = _VM;
    _VM = NULL;
    sq_close( v );
  }
}

void SquirrelVM::PrintFunc( HSQUIRRELVM, const SQChar* s, ... ) {
  static SQChar temp[2048];
  va_list vl;
  va_start( vl, s );
  scvsprintf( temp, s, vl );
  SCPUTS( temp );
  va_end( vl );
}

SquirrelObject SquirrelVM::CompileScript( const SQChar *s ) {
#define MAX_EXPANDED_PATH 1023
  SquirrelObject ret;
  if( SQ_SUCCEEDED( sqstd_loadfile( _VM, s, 1 ) ) ) {
    ret.AttachToStackObject( -1 );
    sq_pop( _VM, 1 );
    return ret;
  }
  throw SquirrelError();
}

SquirrelObject SquirrelVM::CompileBuffer( const SQChar *s, const SQChar * debugInfo ) {
  SquirrelObject ret;
  if( SQ_SUCCEEDED( sq_compilebuffer( _VM, s, ( int )STRLEN( s )*sizeof( SQChar ), debugInfo, 1 ) ) ) {
    ret.AttachToStackObject( -1 );
    sq_pop( _VM, 1 );
    return ret;
  }
  throw SquirrelError();
}

SquirrelObject SquirrelVM::RunScript( const SquirrelObject &o, SquirrelObject *_this ) {
  SquirrelObject ret;
  sq_pushobject( _VM, o._o );
  if( _this ) {
    sq_pushobject( _VM, _this->_o );
  } else {
    sq_pushroottable( _VM );
  }
  if( SQ_SUCCEEDED( sq_call( _VM, 1, SQTrue, SQ_CALL_RAISE_ERROR ) ) ) {
    ret.AttachToStackObject( -1 );
    sq_pop( _VM, 1 );
    return ret;
  }
  sq_pop( _VM, 1 );
  throw SquirrelError();
}


BOOL_T SquirrelVM::BeginCall( const SquirrelObject &func ) {
  if( _CallState != -1 ) {
    return FALSE;
  }
  _CallState = 1;
  sq_pushobject( _VM, func._o );
  sq_pushroottable( _VM );
  return TRUE;
}

BOOL_T SquirrelVM::BeginCall( const SquirrelObject &func, SquirrelObject &_this ) {
  if( _CallState != -1 ) {
    throw SquirrelError( sqT( "call already initialized" ) );
  }
  _CallState = 1;
  sq_pushobject( _VM, func._o );
  sq_pushobject( _VM, _this._o );
  return TRUE;
}

#define _CHECK_CALL_STATE \
  if(_CallState == -1) \
    throw SquirrelError(sqT("call not initialized"));

void SquirrelVM::PushParam( const SquirrelObject &o ) {
  _CHECK_CALL_STATE
  sq_pushobject( _VM, o._o );
  _CallState++;
}

void SquirrelVM::PushParam( const SQChar *s ) {
  _CHECK_CALL_STATE
  sq_pushstring( _VM, s, -1 );
  _CallState++;
}

void SquirrelVM::PushParam( SQInteger n ) {
  _CHECK_CALL_STATE
  sq_pushinteger( _VM, n );
  _CallState++;
}

void SquirrelVM::PushParam( SQFloat f ) {
  _CHECK_CALL_STATE
  sq_pushfloat( _VM, f );
  _CallState++;
}

void SquirrelVM::PushParamNull() {
  _CHECK_CALL_STATE
  sq_pushnull( _VM );
  _CallState++;
}

void SquirrelVM::PushParam( SQUserPointer up ) {
  _CHECK_CALL_STATE
  sq_pushuserpointer( _VM, up );
  _CallState++;
}

SquirrelObject SquirrelVM::EndCall() {
  SquirrelObject ret;
  if( _CallState >= 0 ) {
    SQInteger oldtop = sq_gettop( _VM );
    SQInteger nparams = _CallState;
    _CallState = -1;
    if( SQ_SUCCEEDED( sq_call( _VM, nparams, SQTrue, SQ_CALL_RAISE_ERROR ) ) ) {
      ret.AttachToStackObject( -1 );
      sq_pop( _VM, 2 );
    } else {
      sq_settop( _VM, oldtop - ( nparams + 1 ) );
      throw SquirrelError();
    }
  }
  return ret;
}

SquirrelObject SquirrelVM::CreateInstance( SquirrelObject &oclass ) {
  SquirrelObject ret;
  SQInteger oldtop = sq_gettop( _VM );
  sq_pushobject( _VM, oclass._o );
  if( SQ_FAILED( sq_createinstance( _VM, -1 ) ) ) {
    sq_settop( _VM, oldtop );
    throw SquirrelError();
  }
  ret.AttachToStackObject( -1 );
  sq_pop( _VM, 2 );
  return ret;
}

SquirrelObject SquirrelVM::CreateTable() {
  SquirrelObject ret;
  sq_newtable( _VM );
  ret.AttachToStackObject( -1 );
  sq_pop( _VM, 1 );
  return ret;
}

SquirrelObject SquirrelVM::CreateString( const SQChar *s ) {
  SquirrelObject ret;
  sq_pushstring( _VM, s, -1 );
  ret.AttachToStackObject( -1 );
  sq_pop( _VM, 1 );
  return ret;
}


SquirrelObject SquirrelVM::CreateArray( SQInteger size ) {
  SquirrelObject ret;
  sq_newarray( _VM, size );
  ret.AttachToStackObject( -1 );
  sq_pop( _VM, 1 );
  return ret;
}

SquirrelObject SquirrelVM::CreateFunction( SQFUNCTION func ) {
  SquirrelObject ret;
  sq_newclosure( _VM, func, 0 );
  ret.AttachToStackObject( -1 );
  sq_pop( _VM, 1 );
  return ret;
}

SquirrelObject SquirrelVM::CreateUserData( SQInteger size ) {
  SquirrelObject ret;
  sq_newuserdata( _VM, size );
  ret.AttachToStackObject( -1 );
  sq_pop( _VM, 1 );
  return ret;
}

const SquirrelObject &SquirrelVM::GetRootTable() {
  return *_root;
}

void SquirrelVM::PushRootTable( void ) {
  sq_pushroottable( _VM );
}


SquirrelObject SquirrelVM::CreateFunction( SQFUNCTION func, const SQChar * scriptFuncName, const SQChar * typeMask ) {
  sq_pushstring( _VM, scriptFuncName, -1 );
  sq_newclosure( _VM, func, 0 );
  SquirrelObject ret;
  ret.AttachToStackObject( -1 );
  SQChar tm[64];
  SQChar * ptm = tm;
  SQInteger numParams = SQ_MATCHTYPEMASKSTRING;
  if( typeMask ) {
    if( typeMask[0] == '*' ) {
      ptm       = 0;
      numParams = 0;
    } else {
      if( SCSNPRINTF( tm, sizeof( tm ), sqT( "t|y|x%s" ), typeMask ) < 0 ) {
        throw SquirrelError( sqT( "CreateFunction: typeMask string too long." ) );
      }
    }
  } else {
    SCSNPRINTF( tm, sizeof( tm ), sqT( "%s" ), sqT( "t|y|x" ) );
  }
  if( ptm ) {
    sq_setparamscheck( _VM, numParams, ptm );
  }
  #ifdef _DEBUG
  sq_setnativeclosurename( _VM, -1, scriptFuncName );
  #endif
  sq_createslot( _VM, -3 );
  return ret;
}

SquirrelObject SquirrelVM::CreateFunction( SquirrelObject & so, SQFUNCTION func, const SQChar * scriptFuncName, const SQChar * typeMask ) {
  PushObject( so );
  SquirrelObject ret = CreateFunction( func, scriptFuncName, typeMask );
  Pop( 1 );
  return ret;
}


SquirrelObject SquirrelVM::CreateFunctionGlobal( SQFUNCTION func, const SQChar * scriptFuncName, const SQChar * typeMask ) {
  PushRootTable();
  SquirrelObject ret = CreateFunction( func, scriptFuncName, typeMask );
  Pop( 1 );
  return ret;
}
